using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.Services.Protocols;
using System.Xml;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Globalization;

namespace CrmExtWeb.Core.Diagnostics
{
    /// <summary>
    /// Summary description for ErrorHandler
    /// </summary>
    public class ErrorHandler
    {
        #region FIELDS

        private ErrorDetail _detail;

        #endregion

        #region CONSTRUCTORS

        private ErrorHandler(Uri requestUrl, Exception exception)
        {
            this.Detail = new ErrorDetail();
            if (requestUrl != null)
            {
                this.Detail.RequestUrl = requestUrl.ToString();
                this.Detail.PathAndQuery = requestUrl.PathAndQuery;
            }
            this.Detail.StackTrace = this.PrepareStackTrace(exception);
            if (exception is HttpException)
            {
                this.ParseHtmlErrorMessage(exception as HttpException);
            }
            this.ExtractExceptionDetail(exception);
            if (string.IsNullOrEmpty(this.Detail.Source))
            {
                this.Detail.Source = "Unknown";
            }
            if (string.IsNullOrEmpty(this.Detail.Description))
            {
                this.Detail.Description = exception.Message;
            }
            if (this.Detail.Origin == ErrorOrigin.Application)
            {
                this.Detail.Origin = ErrorOrigin.Application;
            }
            if (this.Detail.Code == 0)
            {
                this.Detail.Code = Marshal.GetHRForException(exception);
            }
        }

        private ErrorHandler(ErrorHandler other)
        {
            throw new System.NotSupportedException();
        }

        #endregion

        #region PROPERTIES

        private ErrorDetail Detail
        {
            get
            {
                return _detail;
            }
            set
            {
                _detail = value;
            }
        }

        #endregion

        #region METHODS

        private bool ExtractExceptionDetail(Exception exception)
        {
            this.Detail.Source = exception.Source;
            if ((exception.InnerException != null) && this.ExtractExceptionDetail(exception.InnerException))
            {
                return true;
            }
            if (exception is HttpException)
            {
                return this.ExtractHttpExceptionDetail(exception as HttpException);
            }
            if (exception is SoapException)
            {
                return this.ExtractSoapExceptionDetail(exception as SoapException);
            }
            return false;
        }
        
        private bool ExtractSoapExceptionDetail(SoapException exception)
        {
            this.Detail.Origin = ErrorOrigin.Platform;
            if (!this.ParseSoapExceptionDetails(exception.Detail))
            {
                this.Detail.Description = exception.Message;
            }
            return true;
        }
        
        private bool ExtractHttpExceptionDetail(HttpException exception)
        {
            this.Detail.Details = exception.Message;
            Exception baseException = exception.GetBaseException();
            if (baseException != null)
            {
                if (string.IsNullOrEmpty(this.Detail.Description))
                {
                    this.Detail.Description = baseException.Message;
                }
                this.Detail.Code = Marshal.GetHRForException(baseException);
            }
            return true;
        }
        
        private string PrepareStackTrace(Exception exception)
        {
            if (exception == null)
            {
                return string.Empty;
            }
            string innerStackTrace = PrepareStackTrace(exception.InnerException);
            string stackTrace = string.Format("[{0}: {1}]\n{2}\n\n", exception.GetType().Name, exception.Message, exception.StackTrace);
            return innerStackTrace + stackTrace;
        }

        private bool ParseSoapExceptionDetails(XmlNode detailNode)
        {
            XmlNode errorNode = detailNode.SelectSingleNode("error");
            if (errorNode == null || errorNode.ChildNodes.Count == 0)
            {
                return false;
            }
            XmlNode node;
            node = errorNode.SelectSingleNode("code");
            if (node != null)
            {
                string code = node.InnerText;
                if(code.StartsWith("0x"))
                {
                    code = code.Substring(2);
                }
                if(!string.IsNullOrEmpty(code))
                {
                    this.Detail.Code = int.Parse(code, NumberStyles.HexNumber);
                }
            }
            node = errorNode.SelectSingleNode("description");
            if (node != null)
            {
                this.Detail.Description = node.InnerText;
            }
            node = errorNode.SelectSingleNode("file");
            if (node != null)
            {
                this.Detail.SourceFile = node.InnerText;
            }
            node = errorNode.SelectSingleNode("line");
            if (node != null)
            {
                this.Detail.LineNumber = node.InnerText;
            }
            node = errorNode.SelectSingleNode("details");
            if (node != null)
            {
                this.Detail.Details = node.InnerText;
            }
            node = detailNode.SelectSingleNode("requesturl");
            if (node != null)
            {
                this.Detail.RequestUrl = node.InnerText;
            }
            node = errorNode.SelectSingleNode("type");
            if (node != null)
            {
                this.Detail.Origin = (ErrorOrigin)Enum.Parse(typeof(ErrorOrigin), node.InnerText);
            }
            node = errorNode.SelectSingleNode("stacktrace");
            if (node != null)
            {
                this.Detail.StackTrace = node.InnerText;
            }
            return true;
        }

        private bool ParseHtmlErrorMessage(HttpException httpEx)
        {
            string htmlErrorMessage = httpEx.GetHtmlErrorMessage();
            if (string.IsNullOrEmpty(htmlErrorMessage))
            {
                return false;
            }
            const string sourceFileTag = "<b> Source File: </b>";
            int iStartIndex = htmlErrorMessage.IndexOf(sourceFileTag);
            if (iStartIndex >= 0)
            {
                int iEndIndex = htmlErrorMessage.IndexOf('\r', iStartIndex);
                int iBreak = htmlErrorMessage.IndexOf("<b>", iStartIndex + sourceFileTag.Length);
                if (iBreak < iEndIndex)
                {
                    iEndIndex = iBreak;
                }
                if (iEndIndex >= 0)
                {
                    iStartIndex += sourceFileTag.Length;
                    this.Detail.SourceFile = htmlErrorMessage.Substring(iStartIndex, iEndIndex - iStartIndex).Trim();
                }
            }
            const string lineTag = "Line: </b>";
            iStartIndex = htmlErrorMessage.IndexOf(lineTag);
            if (iStartIndex >= 0)
            {
                int iEndIndex = htmlErrorMessage.IndexOf('\r', iStartIndex);
                if (iEndIndex >= 0)
                {
                    iStartIndex += lineTag.Length;
                    this.Detail.LineNumber = htmlErrorMessage.Substring(iStartIndex, iEndIndex - iStartIndex).Trim();
                }
            }
            if (httpEx is HttpCompileException)
            {
                const string compilerMsgTag = "<b> Compiler Error Message: </b>";
                iStartIndex = htmlErrorMessage.IndexOf(compilerMsgTag);
                if (iStartIndex >= 0)
                {
                    int iEndIndex = htmlErrorMessage.IndexOf("<br>", iStartIndex + compilerMsgTag.Length);
                    if (iEndIndex >= 0)
                    {
                        iStartIndex += compilerMsgTag.Length;
                        this.Detail.Description = htmlErrorMessage.Substring(iStartIndex, iEndIndex - iStartIndex).Trim();
                    }
                }
            }
            else
            {
                const string descriptionTag = "Description: </b>";
                iStartIndex = htmlErrorMessage.IndexOf(descriptionTag);
                if (iStartIndex >= 0)
                {
                    int iEndIndex = htmlErrorMessage.IndexOf('\r', iStartIndex);
                    if (iEndIndex >= 0)
                    {
                        iStartIndex += descriptionTag.Length;
                        this.Detail.Description = htmlErrorMessage.Substring(iStartIndex, iEndIndex - iStartIndex);
                    }
                }
            }
            return true;
        }

        #endregion

        #region STATIC METHODS

        public static ErrorDetail HandleError(Exception ex, Uri requestUrl)
        {
            ErrorHandler handler = new ErrorHandler(requestUrl, ex);
            ErrorDetail errorDetail = handler.Detail;
            string eventLogSource = System.Web.Hosting.HostingEnvironment.SiteName;
            if (!EventLog.SourceExists(eventLogSource))
            {
                EventLog.CreateEventSource(eventLogSource, "Application");
            }
            string eventLogMessage = errorDetail.ToString();
            EventLog evtLog = new EventLog();
            evtLog.Source = eventLogSource;
            EventLogEntryType entryType = EventLogEntryType.Error;
            switch (ErrorTypeMap.GetFromCode(errorDetail.Code))
            {
                case ErrorType.Information:
                    entryType = EventLogEntryType.Information;
                    break;
                case ErrorType.Warning:
                    entryType = EventLogEntryType.Warning;
                    break;
                case ErrorType.Security:
                    entryType = EventLogEntryType.FailureAudit;
                    break;
                //case ErrorType.Critical:
                //case ErrorType.NotFound:
                default:
                    entryType = EventLogEntryType.Error;
                    break;
            }
            evtLog.WriteEntry(eventLogMessage, entryType);
            return errorDetail;
        }

        #endregion
    }

    public enum ErrorOrigin
    {
        Unknown = 0,
        Application = 1,
        Platform = 3
    }

    public enum ErrorType
    {
        Unknown = 0,
        Information = 1,
        Warning = 2,
        Security = 3,
        Critical = 4,
        NotFound = 5
    }

    public class ErrorMessage
    {
        #region FIELDS

        private int _code;
        private ErrorType _type = ErrorType.Unknown;
        private string _title = string.Empty;
        private string _message = string.Empty;

        #endregion

        #region PROPERTIES

        public int Code
        {
            get { return _code; }
            set { _code = value; }
        }

        public ErrorType Type
        {
            get { return _type; }
            set { _type = value; }
        }

        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }

        public string Message
        {
            get { return _message; }
            set { _message = value; }
        }

        #endregion

        #region STATIC METHODS

        public static ErrorMessage GetFromCode(int errorCode)
        {
            if (errorCode == 0)
            {
                return null;
            }
            object message = HttpContext.GetGlobalResourceObject("Error", MKey(errorCode));
            object title = HttpContext.GetGlobalResourceObject("Error", TKey(errorCode));
            ErrorMessage errorMessage = new ErrorMessage();
            errorMessage.Code = errorCode;
            if(message != null)
            {
                errorMessage.Message = message.ToString();
            }
            if (title != null)
            {
                errorMessage.Title = title.ToString();
            }
            errorMessage.Type = ErrorTypeMap.GetFromCode(errorCode);
            return errorMessage;
        }

        public static ErrorMessage GetFromCode(string hexCode)
        {
            if (hexCode.StartsWith("0x"))
            {
                hexCode = hexCode.Substring(2);
            }
            int errorCode = int.Parse(hexCode, NumberStyles.HexNumber);
            return GetFromCode(errorCode);
        }

        private static string MKey(int code)
        {
            return string.Format("E{0:x8}M", code);
        }

        private static string TKey(int code)
        {
            return string.Format("E{0:x8}T", code);
        }

        #endregion
    }

    public class ErrorTypeMap
    {
        private static System.Collections.Generic.IDictionary<int, ErrorType> _errorTypes = null;

        public static void InitTypeMap()
        {
            _errorTypes = new System.Collections.Generic.Dictionary<int, ErrorType>();
            _errorTypes[unchecked((int)0x80040220)] = ErrorType.Security;
            _errorTypes[unchecked((int)0x80040207)] = ErrorType.Security;
            _errorTypes[unchecked((int)0x80131509)] = ErrorType.Critical;
            _errorTypes[unchecked((int)0x80004005)] = ErrorType.Critical;
            _errorTypes[unchecked((int)0x80040217)] = ErrorType.NotFound;
            _errorTypes[unchecked((int)0x8063110f)] = ErrorType.NotFound;
        }

        public static ErrorType GetFromCode(int errorCode)
        {
            ErrorType errorType = ErrorType.Unknown;
            if (errorCode != 0)
            {
                if (_errorTypes == null)
                {
                    InitTypeMap();
                }
                _errorTypes.TryGetValue(errorCode, out errorType);
            }
            return errorType;
        }

        public static ErrorType GetFromCode(string hexCode)
        {
            if (hexCode.StartsWith("0x"))
            {
                hexCode = hexCode.Substring(2);
            }
            int errorCode = int.Parse(hexCode, NumberStyles.HexNumber);
            return GetFromCode(errorCode);
        }
    }

    public class ErrorDetail
    {
        #region FIELDS

        private int _code;
        private string _requestUrl;
        private string _pathAndQuery;
        private string _description;
        private string _sourceFile;
        private string _lineNumber;
        private string _details;
        private string _stackTrace;
        private ErrorOrigin _origin;
        private string _source;
        private const string MSG_NOT_AVAILABLE = "NotAvailable";

        #endregion

        #region CONSTRUCTORS

        public ErrorDetail()
        {
            this._requestUrl = string.Empty;
            this._pathAndQuery = string.Empty;
            this._description = string.Empty;
            this._sourceFile = MSG_NOT_AVAILABLE;
            this._lineNumber = MSG_NOT_AVAILABLE;
            this._details = MSG_NOT_AVAILABLE;
            this._stackTrace = string.Empty;
            this._origin = ErrorOrigin.Unknown;
            this._source = string.Empty;
        }

        #endregion

        #region PROPERTIES

        public int Code
        {
            get
            {
                return _code;
            }
            set
            {
                _code = value;
            }
        }

        public string RequestUrl
        {
            get
            {
                return _requestUrl;
            }
            set
            {
                _requestUrl = value;
            }
        }

        public string PathAndQuery
        {
            get
            {
                return _pathAndQuery;
            }
            set
            {
                _pathAndQuery = value;
            }
        }

        public string Description
        {
            get
            {
                return _description;
            }
            set
            {
                _description = value;
            }
        }

        public string SourceFile
        {
            get
            {
                return _sourceFile;
            }
            set
            {
                _sourceFile = value;
            }
        }

        public string LineNumber
        {
            get
            {
                return this._lineNumber;
            }
            set
            {
                this._lineNumber = value;
            }
        }

        public string Details
        {
            get
            {
                return _details;
            }
            set
            {
                _details = value;
            }
        }

        public string StackTrace
        {
            get
            {
                return _stackTrace;
            }
            set
            {
                _stackTrace = value;
            }
        }

        public ErrorOrigin Origin
        {
            get
            {
                return _origin;
            }
            set
            {
                _origin = value;
            }
        }

        public string Source
        {
            get
            {
                return _source;
            }
            set
            {
                _source = value;
            }
        }

        public string HexCode
        {
            get
            {
                return String.Format("0x{0:x8}", this.Code);
            }
        }

        #endregion

        #region METHODS

        public ErrorMessage GetErrorMessage()
        {
            return ErrorMessage.GetFromCode(this._code);
        }

        public override string ToString()
        {
            XmlDocument xDoc = new XmlDocument();
            XmlElement docElement = xDoc.CreateElement("error");
            xDoc.AppendChild(docElement);
            XmlElement element;
            element = xDoc.CreateElement("code");
            if (this.Code != 0)
            {
                element.InnerText = this.HexCode;
            }
            docElement.AppendChild(element);
            element = xDoc.CreateElement("details");
            element.InnerText = this.Details;
            docElement.AppendChild(element);
            ErrorMessage errMsg = this.GetErrorMessage();
            if (errMsg != null)
            {
                element = xDoc.CreateElement("displaytitle");
                element.InnerText = errMsg.Title;
                docElement.AppendChild(element);
                element = xDoc.CreateElement("displaytext");
                element.InnerText = errMsg.Message;
                docElement.AppendChild(element);
            }
            element = xDoc.CreateElement("description");
            element.InnerText = this.Description;
            docElement.AppendChild(element);
            element = xDoc.CreateElement("file");
            element.InnerText = this.SourceFile;
            docElement.AppendChild(element);
            element = xDoc.CreateElement("line");
            element.InnerText = this.LineNumber;
            docElement.AppendChild(element);
            element = xDoc.CreateElement("requesturl");
            element.InnerText = this.RequestUrl;
            docElement.AppendChild(element);
            element = xDoc.CreateElement("pathandquery");
            element.InnerText = this.PathAndQuery;
            docElement.AppendChild(element);
            element = xDoc.CreateElement("origin");
            element.InnerText = this.Origin.ToString();
            docElement.AppendChild(element);
            element = xDoc.CreateElement("source");
            element.InnerText = this.Source;
            docElement.AppendChild(element);
            element = xDoc.CreateElement("stacktrace");
            element.InnerText = this.StackTrace;
            docElement.AppendChild(element);
            return xDoc.OuterXml;
        }

        #endregion
    }
}